package controllers.validation.jobs;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import com.sun.org.apache.regexp.internal.recompile;

import models.CNAverage;
import models.NodeConfig;
import models.SensorConfig;
import models.SensorData;
import models.UserConfig;
import play.Logger;
import play.Play;
import play.jobs.Every;
import play.jobs.Job;
import util.MessageTextController;
import util.Persistence;
import ch.bfh.abch.rems.REMSMulticastController;
import ch.bfh.abch.rems.sender.tinkerforge.TFMulticastSender;
import ch.bfh.abcm.rems.gateway.GateWayMessageType;
import ch.bfh.abcm.rems.gateway.GatewayMessage;
import ch.bfh.abcm.rems.sensors.tinkerforge.TFSensorType;
import ch.bfh.abcm.rems.tinkerforge.TFNodeAverageMessage;

public class NodeValidationJob extends AbstratctValidationJob {

    private static final long DEFAULT_NODE_MAX_TIME_DIFF = 15 + 1000;
    private static final long DEFAULTE_MAX_VALUE_USER = 1;
    private static final long DEFAULTE_MIN_VALUE_USER = 0;

    private List<String> nodes;

    public NodeValidationJob(String measurementpoint) {
        super(measurementpoint);
    }

    public void doJob() throws Exception {
        // JOB_START
        boolean noErrorbefore = true;
        Logger.debug("Start NodeValidationJob on Node: %s", NODE_IP);
        ArrayList<Integer> averageList = new ArrayList<Integer>();
        // all nodes from the config
        nodes = NodeConfig.getNodes();
        initialiseLogPrefix();
        validationJobLogger.debug(logprefix + "NODE_JOB_START [ON NODE: " + NODE_IP + ", FOR NODES: " + nodes + "]");

        // NODE_RESPONDING / NODE_NOT_RESPONDING
        for (String node : nodes) {

            CNAverage lastAverage = Persistence.getLastCNAvaerage(node, measurementpoint);
            if (lastAverage == null) {
                validationJobLogger.error("latAverage is for Node " + node + " in Null -->  continue");
                nodeNotrespondigTask(node, sensorType);
                noErrorbefore = false;
                continue;
            }
            long maxTimeDiff = getUserPropertyLong(UserConfig.KEY_NODEAVERAGE_MAX_TIME_DIFF_MS,
                    DEFAULT_NODE_MAX_TIME_DIFF);
            String component = "CNA(Node: " + node + " MP: " + lastAverage.getMeasurementPointIP() + " T: "
                    + sensorType + ")";
            if (!isComponentResponding(lastAverage.getTimestamp(), maxTimeDiff, component)) {
                validationJobLogger.error("latAverage is out of Date, continue");
                nodeNotrespondigTask(node, sensorType);
                noErrorbefore = false;
                continue;
            } else {
                String nr_msg = mtc.getNodeRespondingMesssage(node, sensorType);
                validationJobLogger.info(nr_msg);
                averageList.add(lastAverage.getNodeValue());
            }
        }

        // averageListe my be empty
        if (averageList.isEmpty()) {
            validationJobLogger.error(logprefix + "AverageList is empty cancel NodeValidationJob");
            return;
        }
        int average = calcEffektiveValue(averageList);
        // MP_MEASUREMENT_IN_RANGE / MP_MEASUREMENT_OUT_OF_RANGE
        if (!checkMaxDivergentUser(average, sensorType)) {
            return;
        }

        // MP_MEASUREMENT_OK
        if (noErrorbefore) {
            String mmo_msg = mtc.getMesurementPointOKMessage(measurementpoint, sensorType, average);
            validationJobLogger.info(mmo_msg);
            sendGatewayMessage(mmo_msg, GateWayMessageType.SYSTEM_OK, sensorType);
        }
    }

    private void nodeNotrespondigTask(String node, TFSensorType expectedSensorType) {
        CNAverage bakAverage = Persistence.getLastCNAvaerage(node);
        if (bakAverage != null) {
            // This is for Presentation, if we remove a complete (but only
            // one) MeasurementPoint. We don't want a Node not responding
            // message, if the other MPs are still responding
            long maxTimeDiff = getUserPropertyLong(UserConfig.KEY_NODEAVERAGE_MAX_TIME_DIFF_MS,
                    DEFAULT_NODE_MAX_TIME_DIFF);
            if (isComponentResponding(bakAverage.getTimestamp(), maxTimeDiff, "bakCNA")) {
                // Node is still responding --> MeasurementPoint must be plugged
                // out
                validationJobLogger.warn("MeasurementPoint not Responding");
                String mnr_msg = mtc.getMeasurementPointNotRespondingMesssage(sensorType + "");
                validationJobLogger.error(mnr_msg);
                sendGatewayMessage(mnr_msg, GateWayMessageType.SYSTEM_ERROR, expectedSensorType);
                validationJobLogger.error(logprefix + "MeasurementPoint " + sensorType + " is not responding");
                return;
            }
        }
        String nnr_msg = mtc.getNodeNotRespondingMesssage(node);
        validationJobLogger.error(nnr_msg);
        sendGatewayMessage(nnr_msg, GateWayMessageType.SYSTEM_ERROR, expectedSensorType);
        validationJobLogger.error(logprefix + "Node " + node + " is not responding");
    }

    private void initialiseLogPrefix() {
        List<SensorConfig> config = SensorConfig.getSensors(measurementpoint);
        if (config.size() >= 1) {
            sensorType = config.get(0).getSensorType();
            createLogprefix(NODE_IP);
        } else {
            Logger.error("No Sensor found... WTF");
        }
    }

    private int calcEffektiveValue(ArrayList<Integer> lastAverages) {
        int sum = 0;
        for (Integer integer : lastAverages) {
            sum = sum + integer;
        }
        return sum / lastAverages.size();
    }

    private boolean checkMaxDivergentUser(int average, TFSensorType type) {
        String keyusermax = type + UserConfig.MAX_VALUE_USER_KEY_EXT;
        String keyusermin = type + UserConfig.MIN_VALUE_USER_KEY_EXT;
        Logger.debug(keyusermin + " " + keyusermax);
        int ucmin = getUserPropertyInt(keyusermin, DEFAULTE_MIN_VALUE_USER);
        int ucmax = getUserPropertyInt(keyusermax, DEFAULTE_MAX_VALUE_USER);
        if (!(average >= ucmin && average <= ucmax)) {
            String mmoor_msg = mtc.getMesurementPointOutOfRangeMessage(measurementpoint, type, ucmin, ucmax, average);
            validationJobLogger.warn(mmoor_msg);
            GatewayMessage gatewayMessage = new GatewayMessage(GateWayMessageType.MEASUREMENT_DIVERGENCE, mmoor_msg,
                    sensorType);
            sender.sendGateWayMessage(gatewayMessage);
            return false;
        }
        String mmir_msg = mtc.getMesurementPointInRangeMessage(measurementpoint, type, ucmin, ucmax, average);
        validationJobLogger.info(mmir_msg);
        return true;
    }
}
